﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Timers;
using System.Web;
using System.Windows.Forms;
using HtmlAgilityPack;
using PMC.Interfaces;
using PMC.Scripts;
using PMC.Structs;
using PMC.Urls;
using HtmlDocument = HtmlAgilityPack.HtmlDocument;
using Timer = System.Timers.Timer;

namespace PMC
{
    public class WbrowserWebRequest : IWbrowser, IDisposable
    {
        private Timer timer = new Timer();
        private Timer pageTimer = new Timer();
        private Timer saveTimer = new Timer(2000);
        private Uri reloadUrl;
        private IAsyncResult currentRequest;
        private static CookieContainer cookieJar;
        private HttpWebRequest webRequest;
        protected Control control = new Label(); //new WebBrowser();
        private static bool isInit;
        private const int maxNumRedirects = 5;
        private int currentRedirect;
        //private static string serializePath = Settings.Default.SettingsFolder + "/CookieContainer.bin";
        /// <summary>
        /// Keeps track of the number of browsers that exist, for serializing purposes
        /// </summary>
        private static int numBrowser;

        public WbrowserWebRequest()
        {
            numBrowser++;
            Init();
            //control.ScriptErrorsSuppressed = true;
            //control.Navigate("about:blank");
            timer.Elapsed += timer_Elapsed;
            pageTimer.Elapsed += pageTimer_Elapsed;
            pageTimer.AutoReset = timer.AutoReset = false;
            saveTimer.Elapsed += SaveTimerOnElapsed;
        }

        private const double delayMarge = 1.4;
        private void SaveTimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
        {
            if (webRequest != null)
            {
                IHandleable script = (IHandleable) CurrentWebsite ?? Script.Default;
                if (elapsedEventArgs.SignalTime - TimerElapse > TimeSpan.FromMilliseconds(script.Interval*delayMarge))
                {
                    Next();
                }
            }
        }

        void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Next();
        }

        void pageTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (currentRedirect == maxNumRedirects)
                Next();
            else
            {
                // Navigates to the reload url
                currentRedirect++;
                Navigate(reloadUrl);
            }
        }

        public int TimerInterval
        {
            get { return (int)timer.Interval; }
            set { timer.Interval = value; }
        }

        public DateTime TimerElapse { get; private set; }

        public Website CurrentWebsite { get; private set; }

        private BrowserState state = BrowserState.Stopped;
        public BrowserState State
        {
            get { return state; }
            private set { state = value; OnBrowserStateChanged(); }
        }

        public HtmlDocument Document { get; private set; }

        public string DocumentText
        {
            get { return Document.DocumentNode.OuterHtml; }
        }

        public Uri Url { get; private set; }

        public void Next()
        {
            if (UriCollector.ActiveCollector.Count > 0 && Global.CheckInternetConnection())
            {
                currentRedirect = 0;
                var url = UriCollector.ActiveCollector.Next();
                if (url != null)
                    Navigate(url);
            }
            else
            {
                Stop();
            }
        }

        public Task NextAsync()
        {
            return Task.Factory.StartNew(Next);
        }

        public void Start()
        {
            Log.Instance.AddMessage("Browser activated");
            State = BrowserState.Started;
            saveTimer.Start();
            Next();
        }

        public Task StartAsync()
        {
            return Task.Factory.StartNew(Start);
        }

        public void Stop()
        {
            Log.Instance.AddMessage("Browser ["+GetHashCode()+"] deactivated");
            State = BrowserState.Stopped;
            timer.Stop();
            saveTimer.Stop();
            UriCollector.UrlAdded += UriCollector_UrlAdded;
        }

        void UriCollector_UrlAdded(object sender, UrlEventArgs e)
        {
            Start();
            UriCollector.UrlAdded -= UriCollector_UrlAdded;
        }

        public void Navigate(Uri url)
        {
            url = new Uri(HttpUtility.HtmlDecode(url.ToString()));
            Url = url;
            try
            {
                webRequest = (HttpWebRequest)WebRequest.Create(url);
            }
            catch (Exception e)
            {
                Log.Instance.AddMessage(e);
                Next();
                return;
            }
            webRequest.CookieContainer = cookieJar;
            webRequest.Method = "GET";
            currentRequest = webRequest.BeginGetResponse(DocumentCompleted, webRequest); 
            saveTimer.Start();//todo
        }

        private void DocumentCompleted(IAsyncResult result)
        {
            var initiatedWebRequest = (HttpWebRequest) result.AsyncState;
            //if (currentRequest != result)
            //    return;

            try
            {
                if (result.IsCompleted)
                {
                    var response = initiatedWebRequest.EndGetResponse(result);
                    // Redirects may change the page!
                    Url = response.ResponseUri;
                    var responseStream = response.GetResponseStream();
                    if (responseStream != null)
                    {
                        var document = new HtmlDocument();
                        document.Load(responseStream);
                        Document = document;

                        // Find all frames to visit next
                        var frames = document.DocumentNode.Descendants("frame").Concat(document.DocumentNode.Descendants("iframe"));

                        foreach (var frame in frames)
                        {
                            var src = frame.GetAttributeValue("src", null);
                            if (src != null)
                            {
                                var requestedPage = Global.HtmlUtility.HttpPageRequest(new Uri(Url, HttpUtility.HtmlDecode(src)));
                                if (requestedPage != null)
                                {
                                    frame.InnerHtml = requestedPage.DocumentNode.InnerHtml;
                                }
                            }
                        }

                        // This whole thing is for CashCrusader scripts. Because we wanna refresh the page to receive the earnings
                        var metatags = Document.DocumentNode.SelectNodes("//meta[not(ancestor::noscript)]");
                        if (metatags != null)
                        {
                            foreach (HtmlNode metatag in metatags)
                            {
                                var httpequiv = metatag.GetAttributeValue("HTTP-EQUIV", string.Empty).ToLower();
                                if (httpequiv.Equals("refresh"))
                                {
                                    var splitted = metatag.GetAttributeValue("content", string.Empty).Split(';'); //Todo make safe try catch or tryparse
                                    pageTimer.Interval = double.Parse(splitted[0]) * 1000;
                                    reloadUrl = (splitted.Length > 1) ? new Uri(Url, splitted[1].Substring(4)) : Url;
                                    pageTimer.Start();
                                }
                            } 
                        }

                        CurrentWebsite = Website.WebsiteByUrl(Url);
                        var currentScript = CurrentWebsite == null ? Script.Default : CurrentWebsite.Script;
                        var handle = currentScript.Handle(this);
                        switch (handle.Handler)
                        {
                            case DocumentHandler.Wait:
                                TimerInterval = currentScript.Interval;
                                timer.Start();
                                break;
                            case DocumentHandler.Skip:
                                Next();
                                break;
                            case DocumentHandler.Reload:
                                Navigate(Url);
                                break;
                            case DocumentHandler.Replace:
                                Navigate((Uri)handle.Argument0);
                                break;
                            case DocumentHandler.Post:
                                Navigate(Url);
                                break;
                            case DocumentHandler.NoAction:
                                break;
                        }
                        //MessageBox.Show(DocumentText);
                        var displayDoc = new HtmlDocument();
                        displayDoc.LoadHtml(DocumentText);
                        var displayNodes = displayDoc.DocumentNode;
                        foreach (var descendant in (IEnumerable<HtmlNode>)displayNodes.SelectNodes("//head|//script|//meta|//link|//style|//comment()|//noscript") ?? new List<HtmlNode>(0))
                            descendant.Remove();

                        var displayText = Regex.Replace(displayNodes.InnerText, @"\s+", " ");
                        control.Invoke(new MethodInvoker(() => { control.Text = displayText; }));
                        TimerElapse = DateTime.Now;
                        OnWebpageVisited();
                        saveTimer.Stop();//todo
                    }
                }
            }
            // WebException or whatsoever, just go to the next url!
            catch (Exception ex)
            {
                Log.Instance.AddMessage(ex);
                Next();
            }
        }

        public void SetCookie(Uri url, CookieCollection cookie)
        {
            cookieJar.Add(url, cookie);
        }

        public CookieCollection GetCookies(Uri url)
        {
            return cookieJar.GetCookies(url);
        }

        public CookieCollection GetCookies()
        {
            return cookieJar.GetCookies(Url);
        }

        public event webpageChanged WebpageVisited;
        public void OnWebpageVisited()
        {
            if (WebpageVisited != null)
            {
                WebpageVisited.Invoke(CurrentWebsite, Url);
            }
        }

        public event browserStateChanged BrowserStateChanged;
        public void OnBrowserStateChanged()
        {
            if (BrowserStateChanged != null)
            {
                BrowserStateChanged(state);
            }
        }

        public void Dispose()
        {
            control.Dispose();
            timer.Dispose();
            pageTimer.Dispose();
            saveTimer.Dispose();
            GC.SuppressFinalize(this);
            //if (--numBrowser == 0)
            //    Global.XMLSerialize(serializePath, cookieJar);
        }

        private static void Init()
        {
            if (!isInit)
            {
                cookieJar = Global.CookieJar;//Global.XMLDeserialize<CookieContainer>(serializePath) ?? new CookieContainer();
                //Deserialize cookiejar here 'n stuff!
                isInit = true;
            }
        }

        public static explicit operator Control(WbrowserWebRequest web)
        {
            return web.control;
        }
    }
}
